import { VitePlugin } from "@/utils.ts"
import { readFileSync } from "node:fs"
import { builtinModules } from "node:module"
import { join } from "node:path"
import { ExternalOption, RolldownPlugin } from "rolldown"

/**
 * 包外部依赖的基本选项。
 *
 * 1. 根目录默认为当前工作目录。
 * 2. 排除项优先级高于包含项。
 * 3. 依赖项默认为 true（将被外部化）。
 *
 * 此类型设计用于更高级别的封装。
 * 对于单独使用，您可以参考 {@link PackageExternalOptions}。
 */
export type PackageExternalOptionsBase = {
  excludes?: (string | RegExp)[]
  includes?: (string | RegExp)[]

  dependencies?: boolean
  devDependencies?: boolean
  peerDependencies?: boolean
}

/**
 * 包外部依赖的选项。
 *
 * 1. 根目录默认为当前工作目录。
 * 2. 排除项优先级高于包含项。
 * 3. 依赖项默认为 true（将被外部化）。
 */
export type PackageExternalOptions = PackageExternalOptionsBase & {
  root?: string
}

/**
 * 为打包器生成外部选项函数。
 *
 * 默认情况下会排除所有内置的 Node.js API。
 * 依赖项的外部化将从根目录（`root` 选项，默认为当前工作目录）
 * 的清单文件（package.json）中解析。
 *
 * @param options 包外部选项。
 * @returns 打包器的外部选项函数。
 */
export function packageExternals(options?: PackageExternalOptions) {
  const {
    root = "",
    includes = [],
    excludes = [],
    dependencies = true,
    devDependencies,
    peerDependencies,
  } = options || {}

  const externals: string[] = []
  if (dependencies || devDependencies || peerDependencies) {
    const manifestFile = join(root, "package.json")
    const manifest = JSON.parse(readFileSync(manifestFile, "utf-8"))

    if (dependencies && manifest.dependencies)
      externals.push(...Object.keys(manifest.dependencies))
    if (devDependencies && manifest.devDependencies)
      externals.push(...Object.keys(manifest.devDependencies))
    if (peerDependencies && manifest.peerDependencies) {
      externals.push(...Object.keys(manifest.peerDependencies))
    }
  }

  return ((id, _parentId, isResolved) => {
    // Include and exclude.
    for (const item of excludes) if (id.match(item)) return true
    for (const item of includes) if (id.match(item)) return false

    // Parsed and build-in options.
    if (isResolved) return false
    if (id.match(/^node:/) || externals.includes(id)) return true
    for (const name of externals) if (id.startsWith(`${name}/`)) return true
    if (builtinModules.includes(id)) return true
  }) satisfies ExternalOption
}

/**
 * 一个用于外部依赖的 rolldown 插件。
 *
 * 默认情况下会排除所有内置的 Node.js API。
 * 依赖项的外部化将从根目录（`root` 选项，默认为当前工作目录）
 * 的清单文件（package.json）中解析。
 *
 * @param options 包外部选项。
 * @returns 用于外部依赖的 Rolldown 插件。
 */
export function externals(options?: PackageExternalOptions): RolldownPlugin {
  return {
    name: "externals",
    options(inputOptions) {
      const excludes: (string | RegExp)[] = []
      const external = inputOptions.external
      if (external && typeof external !== "function") {
        excludes.push(...(Array.isArray(external) ? external : [external]))
      }

      return {
        ...inputOptions,
        external: packageExternals({
          ...options,
          excludes: [...excludes, ...(options?.excludes || [])],
        }),
      }
    },
  }
}

/**
 * 一个用于外部依赖的 Vite 兼容插件。
 *
 * 默认情况下会排除所有内置的 Node.js API。
 * 依赖项的外部化将从根目录（`root` 选项，默认为当前工作目录）
 * 的清单文件（package.json）中解析。
 *
 * @param options 包外部选项。
 * @returns 用于外部依赖的 Vite 兼容插件。
 */
export function viteExternals(options?: PackageExternalOptions): VitePlugin {
  return {
    name: "vite-externals",
    config() {
      return {
        build: {
          rollupOptions: {
            external: packageExternals({
              ...options,
              excludes: [...(options?.excludes || [])],
            }),
          },
        },
      }
    },
  }
}
